package com.cc.springboot_test.exception;

import com.cc.springboot_test.bean.common.BusinessException;
import com.cc.springboot_test.bean.common.MyError;
import com.cc.springboot_test.bean.common.MySubError;
import com.cc.springboot_test.bean.constants.Code;
import com.cc.springboot_test.converter.ObjectConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import springfox.documentation.annotations.Cacheable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
public class CommonExceptionHandler extends DefaultHandlerExceptionResolver {

    protected final Logger logger = LoggerFactory.getLogger(CommonExceptionHandler.class);

    @Autowired
    private ObjectConverter objectConverter = null;

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 抛出BusinessException时，去掉多余的堆栈信息
        simpleBusinessException(ex);

        MyError err = this.buildError(ex);
        if (err != null) {
            // 处理一些自定义的异常
            return buildModelAndView(request, response, err);
        }
        // 父类处理
        ModelAndView parentResult = super.resolveException(request, response, handler, ex);
        if (parentResult != null) {
            return parentResult;
        }
        // 未知异常
        err = this.buildGolbalError();
        return this.buildModelAndView(request, response, err);
    }

    // 抛出BusinessException时，去掉多余的堆栈信息
    private void simpleBusinessException(Exception ex) {
        if (ex instanceof BusinessException) {
            if (ex.getStackTrace() != null && ex.getStackTrace().length > 0) {
                logger.warn("WARN CommonExceptionHandler.resolveException {} \n{}", ex.getMessage(), ex.getStackTrace()[0]);
            } else {
                logger.warn("WARN CommonExceptionHandler.resolveException", ex);
            }
        }
    }

    private ModelAndView buildModelAndView(HttpServletRequest request, HttpServletResponse response, MyError err) {
        try {
            this.jsonResponse(err, response);
        } catch (IOException e) {
            logger.error("ERROR CommonExceptionHandler buildModelAndView jsonResponse", e);
        }
        return new ModelAndView();
    }

    private MyError buildError(Exception ex) {
        if (ex instanceof BindException) {
            return this.buildBindExceptionError((BindException) ex);
        } else if (ex instanceof ConstraintViolationException) {
            return this.buildConstrainVioError((ConstraintViolationException) ex);
        } else if (ex instanceof BusinessException) {
            return this.buildBusinessExceptionError((BusinessException) ex);
        } else if (ex instanceof MethodArgumentNotValidException) {
            return this.buildMethodArgumentNotValidException((MethodArgumentNotValidException) ex);
        } else if (ex instanceof HttpMessageConversionException) {
            return new MyError(Code.ERROR_800, "请求参数格式有误");
        }
        return null;
    }

    private MyError buildBindExceptionError(BindException ex) {
        MyError parentErr = new MyError(Code.ERROR_801, "参数校验失败");
        List<FieldError> errors = ex.getFieldErrors();
        for (FieldError error : errors) {
            MySubError subError = new MySubError();
            subError.setCode(error.getCode());
            subError.setMsg(error.getDefaultMessage());
            subError.setField(error.getField());
            parentErr.getFields().add(subError);
        }
        return parentErr;
    }

    private MyError buildConstrainVioError(ConstraintViolationException ex) {
        MyError parentErr = new MyError(Code.ERROR_801, "参数校验失败");
        Set<ConstraintViolation<?>> errors = ex.getConstraintViolations();
        for (ConstraintViolation<?> error : errors) {
            MySubError subError = new MySubError();
            subError.setCode(error.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName());
            subError.setMsg(error.getMessage());
            subError.setField(error.getPropertyPath().toString());
            parentErr.getFields().add(subError);
        }
        return parentErr;
    }

    private MyError buildBusinessExceptionError(BusinessException ex) {
        MyError err;
        if (ex.getData() == null) {
            err = new MyError(ex.getCode() == null ? Code.ERROR_600 : ex.getCode(), ex.getMsg());
        } else {
            err = new MyError(ex.getCode() == null ? Code.ERROR_600 : ex.getCode(), ex.getMsg(), ex.getData());
        }
        return err;
    }

    private MyError buildMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        MyError parentErr = new MyError(Code.ERROR_800, "参数校验失败");
        List<FieldError> errors = ex.getBindingResult().getFieldErrors();
        for (FieldError error : errors) {
            MySubError subError = new MySubError();
            subError.setCode(error.getCode());
            subError.setMsg(error.getDefaultMessage());
            subError.setField(error.getField());
            parentErr.getFields().add(subError);
        }
        return parentErr;
    }

    // 系统错误，返回系统繁忙
    private MyError buildGolbalError() {
        MyError error = new MyError(Code.ERROR_500, "系统繁忙");
        return error;
    }

    // 生产json返回报文
    private void jsonResponse(MyError error, HttpServletResponse response) throws IOException {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put(Code.CODE, error.getCode());
        resultMap.put(Code.MSG, error.getMsg());
        if (error.getFields() != null && error.getFields().size() > 0) {
            resultMap.put("fields", error.getFields());
        }
        if (error.getData() != null) {
            for (String key : error.getData().keySet()) {
                resultMap.put(key, error.getData().get(key));
            }
        }
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().print(objectConverter.writeValueAsString(resultMap));
        response.flushBuffer();
    }
}
